package com.blacktea.cloudgateway.config;

import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @description:
 * @author: black tea
 * @date: 2021/8/3 11:09
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository,ApplicationEventPublisherAware {

    private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());

    private final StringRedisTemplate redisTemplate;

    private ApplicationEventPublisher publisher;

    private void notifyChanged() {
        log.info("动态网关,触发路由信息变动");
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @PostConstruct
    private void initRoutes(){
        log.info("启动网关项目初始化 getaway 路由(routes)信息!");
        redisTemplate.opsForHash().values(RedisConst.GateWayConst.GATEWAT_NAME)
                .forEach(routeDefinition -> {
                    RouteDefinition value = JSONUtil.toBean(JSONUtil.toJsonStr(routeDefinition), RouteDefinition.class);
                    routes.put(value.getId(), value);
                });
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // 每隔30s,会自动调用该方法
        return Flux.fromIterable(this.routes.values());
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        route.flatMap((r) -> {
            if (StringUtils.isEmpty(r.getId())) {
                return Mono.error(new IllegalArgumentException("该网关配置没有id,param="+JSONUtil.toJsonStr(r)));
            } else {
                redisTemplate.opsForHash().put(RedisConst.GateWayConst.GATEWAT_NAME,r.getId(),JSONUtil.toJsonStr(r));
                this.routes.put(r.getId(), r);
                return Mono.empty();
            }
        }).subscribe();
        notifyChanged();
        return Mono.empty();
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        routeId.flatMap((id) -> {
            if (this.routes.containsKey(id)) {
                this.routes.remove(id);
                redisTemplate.opsForHash().delete(RedisConst.GateWayConst.GATEWAT_NAME,id);
                return Mono.empty();
            } else {
                return Mono.defer(() -> {
                    return Mono.error(new NotFoundException("该网关id: " + routeId + "在rules中不存在,当前rules="+JSONUtil.toJsonStr(this.routes)));
                });
            }
        }).subscribe();
        notifyChanged();
        return Mono.empty();
    }
}
